/*
 *  Texture.cpp
 *  MD2 File Loading
 *
 *  Created by Seth Willits on 9/11/04.
 *  Copyright 2004 Freak Software. All rights reserved.
 *
 *  Portions of this file are from the MD2 file loading routines
 *  found in Chapter 18 of OpenGL Game Programming by Kevin Hawkins
 *  and Dave Astle. 
 */

#include "Texture.h"
#include <cmath>


// LoadPCXFile()
// desc: loads a PCX file into memory
unsigned char *LoadPCXFile(char *filename, PCXHEADER *pcxHeader)
{
	int idx = 0;   			// counter index
	int c;    					// used to retrieve a char from the file
	int i;    					// counter index
	int numRepeat; 	
	FILE *filePtr; 			// file handle
	int width;					// pcx width
	int height;    				// pcx height
	unsigned char *pixelData;	// pcx image data
	unsigned char *paletteData;   // pcx palette data

	// open PCX file
	filePtr = fopen(filename, "rb");
	if (filePtr == NULL)
		return NULL;

	// retrieve first character; should be equal to 10
	c = fgetc(filePtr);
	if (c != 10) {
		fclose(filePtr);
		return NULL;
	}

	// retrieve next character; should be equal to 5
	c = fgetc(filePtr);
	if (c != 5) {
		fclose(filePtr);
		return NULL;
	}

	// reposition file pointer to beginning of file
	rewind(filePtr);

	// read 4 characters of data to skip
	fgetc(filePtr);
	fgetc(filePtr);
	fgetc(filePtr);
	fgetc(filePtr);

	// retrieve leftmost x value of PCX
	pcxHeader->xMin = fgetc(filePtr);  	// loword
	pcxHeader->xMin |= fgetc(filePtr) << 8; // hiword

	// retrieve bottom-most y value of PCX
	pcxHeader->yMin = fgetc(filePtr);  	// loword
	pcxHeader->yMin |= fgetc(filePtr) << 8; // hiword

	// retrieve rightmost x value of PCX
	pcxHeader->xMax = fgetc(filePtr);  	// loword
	pcxHeader->xMax |= fgetc(filePtr) << 8; // hiword

	// retrieve topmost y value of PCX
	pcxHeader->yMax = fgetc(filePtr);  	// loword
	pcxHeader->yMax |= fgetc(filePtr) << 8; // hiword

	// calculate the width and height of the PCX
	width = pcxHeader->xMax - pcxHeader->xMin + 1;
	height = pcxHeader->yMax - pcxHeader->yMin + 1;

	// allocate memory for PCX image data
	pixelData = (unsigned char*)malloc(width*height);

	// set file pointer to 128th byte of file, where the PCX image data starts
	fseek(filePtr, 128, SEEK_SET);
	
	// decode the pixel data and store
	while (idx < (width*height)) {
		c = fgetc(filePtr);
		if (c > 0xbf) {
			numRepeat = 0x3f & c;
			c = fgetc(filePtr);

			for (i = 0; i < numRepeat; i++) {
				pixelData[idx++] = c;
			}
		}
		else
			pixelData[idx++] = c;

		fflush(stdout);
	}

	// allocate memory for the PCX image palette
	paletteData = (unsigned char*)malloc(768);

	// palette is the last 769 bytes of the PCX file
	fseek(filePtr, -769, SEEK_END);

	// verify palette; first character should be 12
	c = fgetc(filePtr);
	if (c != 12) {
		fclose(filePtr);
		return NULL;
	}

	// read and store all of palette
	for (i = 0; i < 768; i++) {
		c = fgetc(filePtr);
		paletteData[i] = c;
	}

	// close file and store palette in header
	fclose(filePtr);
	pcxHeader->palette = paletteData;

	// return the pixel image data
	return pixelData;
}

// LoadPCXTexture()
// desc: loads a PCX image file as a texture
MD2Texture *LoadPCXTexture(char *filename)
{
	PCXHEADER texInfo;  		// header of texture
	MD2Texture *thisTexture;  	// the texture
	unsigned char *unscaledData;// used to calculate pcx
	int i;    					// index counter
	int j;    					// index counter
	int width;					// width of texture
	int height;    				// height of texture

	// allocate memory for texture struct
	thisTexture = (MD2Texture*)malloc(sizeof(MD2Texture));
	if (thisTexture == NULL)
		return NULL;

	// load the PCX file into the texture struct
	thisTexture->data = LoadPCXFile(filename, &texInfo);
	if (thisTexture->data == NULL)
	{
		free(thisTexture->data);
		return NULL;
	}

	// store the texture information
	thisTexture->palette = texInfo.palette;
	thisTexture->width = texInfo.xMax - texInfo.xMin + 1;
	thisTexture->height = texInfo.yMax - texInfo.yMin + 1;
	thisTexture->textureType = PCX;

	// allocate memory for the unscaled data
	unscaledData = (unsigned char*)malloc(thisTexture->width*thisTexture->height*4);

	// store the unscaled data via the palette
	for (j = 0; j < thisTexture->height; j++)  {
		for (i = 0; i < thisTexture->width; i++)  {
			unscaledData[4*(j*thisTexture->width+i)+0] = (unsigned char)thisTexture->palette[3*thisTexture->data[j*thisTexture->width+i]+0];
			unscaledData[4*(j*thisTexture->width+i)+1] = (unsigned char)thisTexture->palette[3*thisTexture->data[j*thisTexture->width+i]+1];
			unscaledData[4*(j*thisTexture->width+i)+2] = (unsigned char)thisTexture->palette[3*thisTexture->data[j*thisTexture->width+i]+2];
			unscaledData[4*(j*thisTexture->width+i)+3] = (unsigned char)255;
		}
	}

	// find width and height's nearest greater power of 2
	width = thisTexture->width;
	height = thisTexture->height;

	// find width's
	i = 0;
	while (width) {
		width /= 2;
		i++;
	}
	thisTexture->scaledHeight = (long)pow(2.f, i-1);		//was (long)pow(2,i-1)

	// find height's
	i = 0;
	while (height) {
		height /= 2;
		i++;
	}
	thisTexture->scaledWidth = (long)pow(2.f, i-1);

	// clear the texture data
	if (thisTexture->data != NULL) {
		free(thisTexture->data);
		thisTexture->data = NULL;
	}

	// reallocate memory for the texture data
	thisTexture->data = (unsigned char*)malloc(thisTexture->scaledWidth*thisTexture->scaledHeight*4);
	
	// use the GL utility library to scale the texture to the unscaled dimensions
	gluScaleImage (GL_RGBA, thisTexture->width, thisTexture->height, GL_UNSIGNED_BYTE, unscaledData, thisTexture->scaledWidth, thisTexture->scaledHeight, GL_UNSIGNED_BYTE, thisTexture->data);

	return thisTexture;
}

// LoadTexture()
// desc: loads a texture given the filename
MD2Texture *LoadTexture(char *filename)
{
	MD2Texture *thisTexture = NULL;
	char *extStr;

	// get extension from filename
	extStr = strchr(filename, '.');
	extStr++;

	// set the texture type based on extension of filename
	if ((strcmp(extStr, "PCX") == 0) || (strcmp(extStr, "pcx") == 0) )
		thisTexture = LoadPCXTexture(filename);
	
	return thisTexture;
}
